#include <linux/init.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/time.h>
#include <linux/wait.h>
#include <linux/hrtimer.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include <sound/info.h>
#include <sound/initval.h>


struct tiny_snd {
	struct snd_card *card;
	struct snd_pcm *pcm;
};


static int tiny_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		pr_info("tiny pcm trigger start\n");
		break;
	case SNDRV_PCM_TRIGGER_RESUME:
		pr_info("tiny pcm trigger resume\n");
		break;
	case SNDRV_PCM_TRIGGER_STOP:
		pr_info("tiny pcm trigger stop\n");
		break;
	case SNDRV_PCM_TRIGGER_SUSPEND:
		pr_info("tiny pcm trigger suspend\n");
		break;
	}

	return 0;
}


static int tiny_pcm_prepare(struct snd_pcm_substream *substream)
{
	pr_info("tiny pcm prepare\n");
	return 0;
}


static snd_pcm_uframes_t tiny_pcm_pointer(struct snd_pcm_substream *substream)
{
	pr_info("tiny pcm pointer\n");
	return 0;
}


static const struct snd_pcm_hardware tiny_pcm_hardware = {
	.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID),
	.formats =		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE),
	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,
	.rate_min =		5500,
	.rate_max =		48000,
	.channels_min =		1,
	.channels_max =		2,
	.buffer_bytes_max =	(64*1024),
	.period_bytes_min =	64,
	.period_bytes_max =	(64*1024),
	.periods_min =		1,
	.periods_max =		1024,
	.fifo_size =		0,
};


static int tiny_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
{
	pr_info("tiny pcm hw_params\n");
	return 0;
}

static int tiny_pcm_open(struct snd_pcm_substream *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	pr_info("tiny pcm open\n");

	runtime->hw = tiny_pcm_hardware;

	if (substream->pcm->device & 1) {
		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
	}
	if (substream->pcm->device & 2) {
		runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID);
	}

	return 0;
}


static int tiny_pcm_close(struct snd_pcm_substream *substream)
{
	pr_info("tiny pcm close\n");
	return 0;
}


static const struct snd_pcm_ops tiny_pcm_ops = {
	.open =		   tiny_pcm_open,
	.close =	   tiny_pcm_close,
	.hw_params =   tiny_pcm_hw_params,
	.prepare =	   tiny_pcm_prepare,
	.trigger =	   tiny_pcm_trigger,
	.pointer =	   tiny_pcm_pointer,
};


static int tiny_snd_probe(struct platform_device *pdev)
{
	struct snd_card *card;
	struct tiny_snd *tiny_snd;
	struct snd_pcm *pcm;
	const struct snd_pcm_ops *ops;
	int err;

	pr_info("Tiny snd driver probe\n");

	err = snd_card_new(&pdev->dev, -1, NULL, THIS_MODULE, sizeof(struct tiny_snd), &card);
	if (err < 0)
		return err;

	tiny_snd = card->private_data;
	tiny_snd->card = card;

	err = snd_pcm_new(tiny_snd->card, "Tiny PCM", 0, 1, 1, &pcm);
	if (err < 0)
		return err;

	tiny_snd->pcm = pcm;
	ops = &tiny_pcm_ops;

	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, ops);
	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, ops);

	pcm->private_data = tiny_snd;
	pcm->info_flags = 0;

	strcpy(pcm->name, "Tiny PCM");
	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, NULL, 0, 64*1024);

	strcpy(card->driver, "Tiny");
	strcpy(card->shortname, "Tiny");
	sprintf(card->longname, "Tiny 0");

	err = snd_card_register(card);
	if (err < 0)
		return err;
	platform_set_drvdata(pdev, card);

	return 0;
}


static struct platform_driver tiny_snd_driver = {
	.probe		= tiny_snd_probe,
	.driver		= {
		.name	= "tiny_snd",
	},
};


struct platform_device *device;

static int __init tiny_card_init(void)
{
	int err  = platform_driver_register(&tiny_snd_driver);
	if (err < 0)
		return err;

	device = platform_device_register_simple("tiny_snd", 0, NULL, 0);
	return 0;
}

static void __exit tiny_card_exit(void)
{
	platform_device_unregister(device);
	platform_driver_unregister(&tiny_snd_driver);
}

module_init(tiny_card_init)
module_exit(tiny_card_exit)

MODULE_DESCRIPTION("Tiny ALSA demo");
MODULE_LICENSE("GPL");
